module cacheControlIns
#(parameter SDRAM_SIZE_WIDTH=0,CACHE_SIZE_WIDTH=0, CACHE_SIZE_DWORDS=0, SET_COUNT=0, SET_WIDTH=0, WAY_COUNT=0,WAY_WIDTH=0
)
(
  input                             clk              ,
  input                             reset_n          ,
  input                             isVirtual,
  input [SDRAM_SIZE_WIDTH-2-1:0]    avs_s0_address,        //    s0.address//不包括低两位 24bit = 16M dword 64M byte    128M虚拟空间？
  input                             avs_s0_request,        //      .read
  output  [31:0]                    avs_s0_readdata,       //      .readdata
  output                            avs_s0_waitrequest,    //      .waitrequest

  output  [31:0]                    avs_s0_auto_fetch_address     ,     //    s0.address//不包括低两位
  input                             avs_s0_hold_value,

  output reg                        sdram_req,
  input                             sdram_ack,
  output            [SET_WIDTH-1:0] curr_set_id,
  output reg        [WAY_COUNT-1:0] current_slot,
  output            [WAY_WIDTH-1:0] cache_hited_addr,
  output [SET_WIDTH+CACHE_SIZE_WIDTH-1:0] cache_addr,
  input                      [31:0] cache_hit_data,
  output reg                        is_single                   ,
  input      [31:0]                 readBuffer                  ,
  output     [SDRAM_SIZE_WIDTH-1:0] rdwr_sdram_addr             ,//26bit, 64M word
  output reg [SDRAM_SIZE_WIDTH-1:0] rdwr_sdram_single_addr      ,
  
  
  input   [15:0]     page_table_pos,
  input              refresh_ins

  //TODO GROUP_COUNT <> refresh_idx
);



assign curr_set_id = avs_s0_address[SET_WIDTH+CACHE_SIZE_WIDTH-1:CACHE_SIZE_WIDTH];

localparam ADDR_HIGH_WIDTH = SDRAM_SIZE_WIDTH-2-CACHE_SIZE_WIDTH-SET_WIDTH;
localparam TAG_WIDTH = (1 + 1 + WAY_WIDTH + ADDR_HIGH_WIDTH) * WAY_COUNT;

wire [ADDR_HIGH_WIDTH-1:0]  current_cacheAddrHigh = cacheAddrHigh[current_slot_addr];

assign cache_addr = {curr_set_id, (avs_s0_waitrequest || avs_s0_hold_value) ? avs_s0_address[CACHE_SIZE_WIDTH-1:0] : auto_fetch_address};

wire [TAG_WIDTH - 1 : 0] cacheTag_out;
wire [TAG_WIDTH - 1 : 0] cacheTag_in;
initial begin
  $display("fdsa ins cache width, count: %d, %d", TAG_WIDTH, 2**SET_WIDTH);
end
reg  [TAG_WIDTH-1:0] tagmem[2**SET_WIDTH-1:0];

wire [ADDR_HIGH_WIDTH-1:0] avs_s0_address_high = avs_s0_address[SDRAM_SIZE_WIDTH-2-1:CACHE_SIZE_WIDTH+SET_WIDTH];


wire                             cacheValid   [0:WAY_COUNT-1];//1
reg                              cacheValid_in[0:WAY_COUNT-1];//1
wire [WAY_WIDTH-1:0]             cache_life   [0:WAY_COUNT-1];//1
reg  [WAY_WIDTH-1:0]             cache_life_in[0:WAY_COUNT-1];//1
wire                           cacheVirtual   [0:WAY_COUNT-1];//1
reg                            cacheVirtual_in[0:WAY_COUNT-1];//1
wire [ADDR_HIGH_WIDTH-1:0]    cacheAddrHigh   [0:WAY_COUNT-1];//15
reg  [ADDR_HIGH_WIDTH-1:0]    cacheAddrHigh_in[0:WAY_COUNT-1];//15

wire [WAY_COUNT-1:0] cache_hited;
wire [WAY_COUNT-1:0] cache_hited_raw;
wire [WAY_COUNT-1:0] free_cache_temp;

  assign    {
      cacheValid[1],   cacheValid[0],
      cache_life[1],   cache_life[0],
    cacheVirtual[1], cacheVirtual[0],
   cacheAddrHigh[1],cacheAddrHigh[0]
  } = cacheTag_out;
  //更新tag
  assign cacheTag_in = {
      cacheValid_in[1],   cacheValid_in[0],
      cache_life_in[1],   cache_life_in[0],
    cacheVirtual_in[1], cacheVirtual_in[0],
   cacheAddrHigh_in[1],cacheAddrHigh_in[0]
  };

reg set_cache_life;
reg set_cacheAddrHigh;

wire cacheTag_we = set_cache_life | set_cacheAddrHigh;


genvar i;
generate
  for(i=0; i<WAY_COUNT; i=i+1) begin:BLOCK0
  
    assign cache_hited_raw[i] = cacheValid[i] && avs_s0_address_high == cacheAddrHigh[i] && cacheVirtual[i] == isVirtual;
    assign free_cache_temp[i] = cache_life[i] == 0;

    always@(*) begin
      if(current_slot[i])begin
        
        if(set_invalid) begin
          cacheValid_in[i] <= 0;
        end else if(set_cache_life || set_cacheAddrHigh)begin
          cacheValid_in[i] <= 1;
        end else begin
          cacheValid_in[i] <= cacheValid[i];
        end

        if(set_invalid) begin
          cache_life_in[i] <= 0;
        end else if(set_cache_life || set_cacheAddrHigh)begin
          cache_life_in[i] <= 1;
        end else begin
          cache_life_in[i] <= cache_life[i];
        end
        
        if(set_cacheAddrHigh)begin
           cacheVirtual_in[i] <= isVirtual;
          cacheAddrHigh_in[i] <= avs_s0_address_high;
        end else begin
           cacheVirtual_in[i] <= cacheVirtual[i];
          cacheAddrHigh_in[i] <= cacheAddrHigh[i];
        end
        
      end else begin
        if(set_invalid) begin
          cacheValid_in[i] <= 0;
        end else begin
          cacheValid_in[i] <= cacheValid[i];
        end

        if(set_cache_life || set_cacheAddrHigh)begin
          cache_life_in[i] <= 0;
        end else begin
          cache_life_in[i] <= cache_life[i];
        end

         cacheVirtual_in[i] <= cacheVirtual[i];
        cacheAddrHigh_in[i] <= cacheAddrHigh[i];
      end
    end

  end//for
endgenerate

reg  [SET_WIDTH-1:0] curr_set_id_write_reg;

///////////////////////////
reg set_invalid;
reg   [SET_WIDTH-1:0]     set_invalid_set_index;

wire wren_a = set_invalid ? 1'b1 : cacheTag_we;
wire [SET_WIDTH-1:0] address_w = set_invalid ? set_invalid_set_index : curr_set_id_write_reg;
//    cacheValid    cacheVirtual  cache_life   cacheAddrHigh
reg [SET_WIDTH-1:0] address_r_buff;
always @(posedge clk)
begin
    address_r_buff <= curr_set_id;
end
always @(posedge clk)
begin
  if (wren_a) begin
      tagmem[address_w] <= cacheTag_in;
  end  
end
assign cacheTag_out = tagmem[address_r_buff];
///////////////////////////

singleOut2 ins_cache_hited(
.in(cache_hited_raw),
.out(cache_hited),
.addr(cache_hited_addr)
);

wire [WAY_COUNT-1:0] free_cache;
singleOut2 ins_free_cache(
  .in(free_cache_temp),
  .out(free_cache),
  .def(1)
);

wire [WAY_WIDTH-1:0] current_slot_addr;
singleOut2 ins_current_slot_addr(
  .in(current_slot),
  .addr(current_slot_addr)
);


reg [3:0]  interface_status;
localparam STATUS_INIT            = 0;
localparam STATUS_STEP2           = 1;
localparam STATUS_GET_PHY         = 2;
localparam STATUS_GET_PHY_WAIT    = 3;
localparam STATUS_GET_PHY_WAIT_2  = 4;
localparam STATUS_READ_CACHE      = 5;
localparam STATUS_READ_CACHE_NEXT = 6;
localparam STATUS_READ_CACHE_WAIT = 7;
localparam STATUS_REFRESH         = 8;
localparam STATUS_REFRESH2        = 9;

reg        flushCountPlus1;

assign avs_s0_readdata = cache_hit_data;

reg address_type;

wire [SDRAM_SIZE_WIDTH-1:0] base_addr = {avs_s0_address_high, curr_set_id, {CACHE_SIZE_WIDTH{1'b0}},2'b0};

assign rdwr_sdram_addr = (address_type == 0) ? 
  base_addr : 
  {physical_addr, base_addr[9:0]};//  / phy_address

reg avs_s0_ack;
wire final_ack = ready && (cache_hited!=0);

assign avs_s0_waitrequest = (avs_s0_request && !final_ack);


//响应后，自动预取下一条数据，不用等待请求，同时呈递地址
//                                                     (30-23-2=5),15                      8                 ,2
assign avs_s0_auto_fetch_address = {auto_fetch_virtual,       5'b0,auto_fetch_address_high,auto_fetch_address,2'b0};
reg    [CACHE_SIZE_WIDTH-1:0] auto_fetch_address;
reg [SDRAM_SIZE_WIDTH-2-CACHE_SIZE_WIDTH-1:0] auto_fetch_address_high;
reg          auto_fetch_virtual;


wire [15:0] virtual_addr = avs_s0_address[SDRAM_SIZE_WIDTH-2-1:8];

reg [15:0] physical_addr;

reg ready;

reg sdram_ack_buff;
reg refresh_ins_dly;
reg refresh_req;
always@(posedge clk or negedge reset_n) begin
  if(!reset_n) begin
    sdram_ack_buff <= 0;
    avs_s0_ack <= 0;

    set_cache_life <= 0;
    set_cacheAddrHigh <= 0;

    curr_set_id_write_reg <= 0;
    
    interface_status <= 0;

    sdram_req <= 0;
    is_single <= 0;

    ready <= 0;
    set_invalid <= 0;
    set_invalid_set_index <= 0;
    current_slot <= 0;
    refresh_req <= 0;
    address_type <= 0;
    rdwr_sdram_single_addr <= 0;

    auto_fetch_address <= 0;
    auto_fetch_virtual <= 0;
    auto_fetch_address_high <= 0;
    refresh_ins_dly <= 0;
  end else begin
    sdram_ack_buff <= sdram_ack;
    avs_s0_ack <= 0;
    refresh_ins_dly <= refresh_ins;
    set_cache_life <= 0;
    set_cacheAddrHigh <= 0;
    curr_set_id_write_reg <= curr_set_id;
    auto_fetch_address <= auto_fetch_address + 1'b1;


    if(refresh_ins && !refresh_ins_dly) begin
      refresh_req <= 1;
    end


    case (interface_status)
    STATUS_INIT : begin//初始化
      ready <= 0;
      set_invalid <= 0;
      if(refresh_req)begin
        interface_status <= STATUS_REFRESH;
      end else if(avs_s0_request && !avs_s0_ack)begin
        ready <= 1;
        auto_fetch_virtual <= isVirtual;
        auto_fetch_address_high <= avs_s0_address[SDRAM_SIZE_WIDTH-2-1:CACHE_SIZE_WIDTH];
        auto_fetch_address <= avs_s0_address[CACHE_SIZE_WIDTH-1:0]+1'b1;
        interface_status <= STATUS_STEP2;
      end
    end

    STATUS_REFRESH: begin
      set_invalid <= 1;
      refresh_req <= 0;
      set_invalid_set_index <= 1;
      interface_status <= STATUS_REFRESH2;
    end
    STATUS_REFRESH2: begin
      set_invalid_set_index <= set_invalid_set_index + 1'b1;
      if(set_invalid_set_index == 0)begin
        interface_status <= STATUS_INIT;
      end
    end

    STATUS_STEP2: begin
      if(cache_hited)begin            //再延迟一周期才能判断
        current_slot <= cache_hited;
        set_cache_life <= 1;
        ready <= 0;
        interface_status <= STATUS_INIT;
        avs_s0_ack <= 1;
      end else begin
        current_slot <= free_cache;
        interface_status <= STATUS_READ_CACHE;
      end
    end
    
    STATUS_READ_CACHE: begin
      if(isVirtual)begin
        interface_status <= STATUS_GET_PHY;
        address_type <= 1;
      end else begin
        interface_status <= STATUS_READ_CACHE_NEXT;
        address_type <= 0;
      end
    end
    
    STATUS_GET_PHY: begin
      rdwr_sdram_single_addr <= {page_table_pos, virtual_addr[15:8], 2'b0};
      sdram_req <= 1;//GET_PHY
      is_single <= 1;
      interface_status <= STATUS_GET_PHY_WAIT;
    end

    STATUS_GET_PHY_WAIT: begin
      if(sdram_ack_buff)begin
        rdwr_sdram_single_addr <= {readBuffer[15:0], virtual_addr[7:0], 2'b0};
      end
      if(!sdram_req && !sdram_ack_buff)begin
        sdram_req <= 1;//GET_PHY
        is_single <= 1;
        interface_status <= STATUS_GET_PHY_WAIT_2;
      end
    end
    
    STATUS_GET_PHY_WAIT_2: begin
      if(sdram_ack_buff)begin
        physical_addr <= readBuffer[15:0];
      end
      if(!sdram_req && !sdram_ack_buff)begin
        interface_status <= STATUS_READ_CACHE_NEXT;
      end
    end

    STATUS_READ_CACHE_NEXT: begin
      sdram_req <= 1;//读缓存
      is_single <= 0;
      interface_status <= STATUS_READ_CACHE_WAIT;
    end
    
    STATUS_READ_CACHE_WAIT: begin
      if(sdram_ack_buff)begin
        // 更新tag （未命中，完成读写内存，当前life=1，另一边置0， 更新当前tag地址，）
        set_cacheAddrHigh <= 1;//当前地址写入缓存地址高
        set_cache_life <= 1;
        interface_status <= STATUS_INIT;
        avs_s0_ack <= 1;
      end
    end
    
    endcase

    if(sdram_ack_buff && sdram_req)begin
      sdram_req <= 0;
    end

  end
end


endmodule
